﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using XDL.Framework;
using XDL.Framework.OpenSteer;

namespace JumpNFight.Simulation
{
    class Level : 
        IMap,
        ITiledMap
    {
        /// <summary>
        /// Helper structure to support A* resolution
        /// </summary>
        struct MapCell
        {
            public int Tag0;
            public int Tag1;
            public int Cost;
        }

        Random m_rnd = new Random();

        TileMap m_map;
        MapCell[] m_cells;
        List<Vector2i> m_crateSpawnTiles;
        List<Vector2i> m_fireTiles;
        Vector2i[] m_spawnPos = new Vector2i[World.MaxPlayers];
        char[] Tiles;
        public Level(TileMap map)
        {
            m_map = map;
            m_cells = new MapCell[m_map.Width * m_map.Height];
            // default cell cost
            for (int k = 0; k < m_cells.Length; k++)
            {
                m_cells[k].Cost = 1;
                m_cells[k].Tag0 = 0;
                m_cells[k].Tag1 = 0;
            }
            m_crateSpawnTiles = m_map.GetTilesByType("CRATE");
            m_fireTiles = m_map.GetTilesByType("FIRE");
            m_spawnPos[0] = m_map.GetTilesByType("PLAYER1")[0];
            m_spawnPos[1] = m_map.GetTilesByType("PLAYER2")[0];

            Tiles = new char[m_map.Width * m_map.Height];
            foreach (Vector2i it in m_fireTiles)
            {
                Tiles[it.X + it.Y * m_map.Width] = '.';
            }
            foreach (Vector2i it in m_map.GetTilesByType("X"))
            {
                Tiles[it.X + it.Y * m_map.Width] = 'X';
            }
        }

        void IncQuadrantCost(int i, int j)
        {
            Vector2i[] quadrants = {
                                          new Vector2i(1,0),
                                          new Vector2i(0,1),
                                          new Vector2i(-1,0),
                                          new Vector2i(0,-1),
                                          new Vector2i(1,-1),
                                          new Vector2i(-1,-1),
                                          new Vector2i(1,1),
                                          new Vector2i(-1,1)};
            foreach (Vector2i it in quadrants)
                IncCost(it.X + i, it.Y + j);
        }

        void IncCost(int i, int j)
        {
            if (i < 0 || i >= m_map.Width)
                return;
            if (j < 0 || j >= m_map.Height)
                return;
            m_cells[i + j * m_map.Width].Cost = 5;
        }

        char GetTile(int i, int j)
        {
            return Tiles[i + j * m_map.Width];
        }

        Nullable<char> TryGetTile(int tilex, int tiley)
        {
            if (tilex < 0 || tilex >= m_map.Width)
                return 'X';
            if (tiley < 0 || tiley >= m_map.Height)
                return 'X';
            //
            return GetTile(tilex, tiley);
        }

        /// <summary>
        /// Returns the tile to spawn a player.
        /// </summary>
        /// <param name="index">Player index</param>
        /// <returns></returns>
        public Vector2i GetPlayerSpawnPoint(PlayerIndex index)
        {
            return m_spawnPos[(int)index];
        }

        public List<Vector2i> FireTiles
        {
            get
            {
                return m_fireTiles;
            }
        }

        #region Implements ITiledMap
        /// <summary>
        /// Returns whether the given tile coordinate can be walked on.
        /// </summary>
        /// <param name="x">Tile X</param>
        /// <param name="y">Tile Y</param>
        /// <returns></returns>
        public bool IsWalkable(int x, int y)
        {            
            Nullable<char> ch = TryGetTile(x, y);
            return ch != 'X';
        }

        public bool IsNotWalkable(int x, int y)
        {
            return TryGetTile(x, y) == 'X';
        }

        /// <summary>
        /// Returns whether the given tile coordinate is deadly.
        /// </summary>
        /// <param name="x">Tile X</param>
        /// <param name="y">Tile Y</param>
        /// <returns></returns>
        public bool IsDeadly(int x, int y)
        {
            Nullable<char> ch = TryGetTile(x, y);
            return ch == '.';
        }
        #endregion

        int m_lastSpawnPos = -1;
        /// <summary>
        /// Returns a new random crate spawn point.
        /// </summary>
        /// <returns>A tile position packed in a <see cref="Vector2i"/>.</returns>
        public Vector2i GetCrateSpawn()
        {
            // avoid crate respawning at the very same place!
            int nextSpawn = m_rnd.Next(m_crateSpawnTiles.Count);
            if ( m_lastSpawnPos == nextSpawn )
                nextSpawn = (nextSpawn+1) % m_crateSpawnTiles.Count;
            m_lastSpawnPos = nextSpawn;
            return m_crateSpawnTiles[m_lastSpawnPos];
        }

        public int Width
        {
            get { return m_map.Width; }
        }

        public int Height
        {
            get { return m_map.Height; }
        }

        public int GetCost(Vector2i pos)
        {
            return m_cells[pos.X + pos.Y * m_map.Width].Cost;
        }

        public bool IsWalkable(Vector2i pos)
        {
            return IsWalkable(pos.X,pos.Y);
        }

        public float GetHeight(Vector2i pos)
        {
            return 0;
        }

        public float GetHeight(int x, int y)
        {
            return 0;
        }

        public void Tag(Vector2i pos, int tag, int tagCategory)
        {
            switch (tagCategory)
            {
                case 0:
                    m_cells[pos.X + pos.Y * m_map.Width].Tag0 = tag;
                    break;
                case 1:
                    m_cells[pos.X + pos.Y * m_map.Width].Tag1 = tag;
                    break;
                default:
                    throw new ArgumentException("Invalid tag identifier");
            }
        }

        public bool IsTagged(Vector2i pos, int tag, int tagCategory)
        {
            switch (tagCategory)
            {
                case 0:
                    return m_cells[pos.X + pos.Y * m_map.Width].Tag0 == tag;
                case 1:
                    return m_cells[pos.X + pos.Y * m_map.Width].Tag1 == tag;
                default:
                    throw new ArgumentException("Invalid tag identifier");
            }
        }
    }
}
